<template>
  <scroll-view
    class="scroyy"
    :scroll-top="scrollTop"
    @touchstart="onTouchStart"
    @touchmove="onTouchMove"
    @touchend="onTouchEnd"
    @scroll="onScroll"
    @scrolltoupper="onScrollToTop"
    @scrolltolower="onScrollToBottom"
    scroll-y
    enable-back-to-top
    enhanced
    scroll-with-animation
    :bounces="false"
    :style="{ height: props.height + 'rpx' }"
  >
    <view
      :class="['scroyy__track', 'scroyy__track--' + (loosing ? 'loosing' : '')]"
      :style="{ transform: `translate3d(0, ${_barHeight}rpx, 0)` }"
    >
      <view
        class="scroyy__tips"
        :class="['scroyy__track--' + (loosing ? 'loosing' : '')]"
        :style="{ height: _barHeight + 'rpx' }"
      >
        <slot name="pull" :status="{ refreshStatus }">
          <view v-if="refreshStatus === 2" class="flex flex-row flex-row-center-center">
            <tm-icon :font-size="24" color="primary" name="tmicon-shuaxin" spin></tm-icon>
            <tm-text
              color="grey"
              _class="pl-16"
              :label="loadingTexts[refreshStatus]"
            ></tm-text>
          </view>
          <view
            v-if="refreshStatus != -1 && refreshStatus != 2"
            class="flex flex-row flex-row-center-center srrryration"
            :style="{
              opacity: `${refreshStatus == 0 ? _barHeight / props.loadBarHeight : 1}`,
            }"
          >
            <view
              :class="
                refreshStatus == 0
                  ? 'srrryration srrryrationOn'
                  : 'srrryration srrryrationOf'
              "
            >
              <tm-icon
                :font-size="24"
                color="primary"
                name="tmicon-long-arrow-down"
              ></tm-icon>
            </view>
            <tm-text
              color="grey"
              _class="pl-16"
              :label="loadingTexts[refreshStatus]"
            ></tm-text>
          </view>
        </slot>
      </view>
      <slot></slot>
      <view
        :class="['scroyy__track--loosing flex ']"
        :style="{ height: (isBootRefresh ? props.loadBarHeight : 0) + 'rpx' }"
      >
        <slot name="bottom" :status="{ isBootRefresh }">
          <view
            v-if="isBootRefresh"
            class="flex flex-row flex-row-center-center"
            :style="{ height: (isBootRefresh ? props.loadBarHeight : 0) + 'rpx' }"
          >
            <tm-icon :font-size="24" color="primary" name="tmicon-shuaxin" spin></tm-icon>
            <tm-text color="grey" _class="pl-16" label="数据加载中"></tm-text>
          </view>
        </slot>
      </view>
    </view>
  </scroll-view>
</template>

<script lang="ts" setup>
import tmIcon from "../tm-icon/tm-icon.vue";
import tmText from "../tm-text/tm-text.vue";
import { getCurrentInstance, nextTick, onMounted, ref, Ref, watch } from "vue";
import { propsdetail } from "./propsdetail";
const proxy = getCurrentInstance()?.proxy ?? null;
const emits = defineEmits([
  "bottom",
  "change",
  "refresh",
  "timeout",
  "update:modelValue",
  "update:bottomValue",
]);
const props = defineProps({
  ...propsdetail,
});
// 下拉开始的起点，主要用于计算下拉高度
const startPoint: Ref<{
  pageX: number;
  pageY: number;
} | null> = ref(null);
const isPulling = ref(false); // 是否下拉中
const _maxBarHeight = ref(props.maxBarHeight); // 最大下拉高度，单位 rpx
// 触发刷新的下拉高度，单位rpx
// 松开时下拉高度大于这个值即会触发刷新，触发刷新后松开，会恢复到这个高度并保持，直到刷新结束
const _barHeight = ref(0);
/** 开始刷新 - 刷新成功/失败 最大间隔时间setTimeout句柄 */
let maxRefreshAnimateTimeFlag: number | null = 0;
/** 关闭动画耗时setTimeout句柄 */
let closingAnimateTimeFlag: number | null = 0;
//加载框的高度
const refreshStatus = ref(-1);
const loosing = ref(false);
const enableToRefresh = ref(true);
const scrollTop = ref(0);

/** 触底下拉刷新参数。 */
const isBootRefresh = ref(props.bottomValue);

watch(
  () => props.modelValue,
  () => {
    if (!props.modelValue) {
      if (maxRefreshAnimateTimeFlag != null) {
        clearTimeout(maxRefreshAnimateTimeFlag);
      }
      refreshStatus.value = 3;
      close();
    }
  }
);
watch(
  () => props.bottomValue,
  () => {
    isBootRefresh.value = props.bottomValue;
  }
);
onMounted(() => {
  clearTimeout(maxRefreshAnimateTimeFlag);
  clearTimeout(closingAnimateTimeFlag);
  nextTick(() => setDefault());
});

function setDefault() {
  if (props.defaultValue) {
    setRefreshBarHeight(props.loadBarHeight);
    refreshStatus.value = 2;
    loosing.value = true;
    isPulling.value = true;
    enableToRefresh.value = false;
    startPoint.value = null;
  }
}

function onScrollToBottom() {
  if (isBootRefresh.value) return;
  emits("update:bottomValue");
  emits("bottom");
}

function onScrollToTop() {
  enableToRefresh.value = true;
}

function onScroll(e: any) {
  enableToRefresh.value = e.detail?.scrollTop === 0;
}

function setScrollTop(tp: number) {
  scrollTop.value = tp;
}

function scrollToTop() {
  setScrollTop(0);
}

function onTouchStart(e: WechatMiniprogram.Component.TrivialInstance) {
  if (isPulling.value || !enableToRefresh.value) return;
  const { touches } = e;
  if (touches.length !== 1) return;
  const { pageX, pageY } = touches[0];

  loosing.value = false;
  startPoint.value = {
    pageX,
    pageY,
  };
  isPulling.value = true;
}

function onTouchMove(e: WechatMiniprogram.Component.TrivialInstance) {
  if (!startPoint.value) return;
  const { touches } = e;

  if (touches.length !== 1) return;

  const { pageY } = touches[0];
  const offset = pageY - startPoint.value.pageY;
  const barsHeight = uni.$tm.u.torpx(offset);

  if (barsHeight > 0) {
    if (barsHeight > _maxBarHeight.value) {
      // 限高
      setRefreshBarHeight(_maxBarHeight.value);
      // this.startPoint.pageY = pageY - this.toPx(this.maxBarHeight); // 限高的同时修正起点，避免触摸点上移时无效果
    } else {
      setRefreshBarHeight(barsHeight);
    }
  }
}

function onTouchEnd(e: WechatMiniprogram.Component.TrivialInstance) {
  if (!startPoint.value) return;
  const { changedTouches } = e;
  if (changedTouches.length !== 1) return;
  const { pageY } = changedTouches[0];
  const barsHeight = uni.$tm.u.torpx(pageY - startPoint.value.pageY);
  startPoint.value = null; // 清掉起点，之后将忽略touchMove、touchEnd事件

  loosing.value = true;
  isBootRefresh.value = false;
  // 松开时高度超过阈值则触发刷新
  if (barsHeight > props.loadBarHeight) {
    _barHeight.value = props.loadBarHeight;
    refreshStatus.value = 2;

    emits("change", true);
    emits("update:modelValue", true);
    emits("refresh");
    maxRefreshAnimateTimeFlag = (setTimeout(() => {
      maxRefreshAnimateTimeFlag = null;

      if (refreshStatus.value === 2) {
        // 超时回调
        emits("timeout");
        close(); // 超时仍未被回调，则直接结束下拉
      }
    }, props.refreshTimeout as any) as any) as number;
  } else {
    close();
  }
}

function setRefreshBarHeight(barsHeight: number) {
  if (barsHeight >= props.loadBarHeight) {
    refreshStatus.value = 1;
  } else {
    refreshStatus.value = 0;
  }
  return new Promise((resolve) => {
    _barHeight.value = barsHeight;
    nextTick(() => {
      resolve(barsHeight);
    });
  });
}

function close() {
  const animationDuration = 350;
  _barHeight.value = 0;
  emits("change", false);
  emits("update:modelValue", false);
  closingAnimateTimeFlag = (setTimeout(() => {
    closingAnimateTimeFlag = null;
    refreshStatus.value = -1;
    isPulling.value = false; // 退出下拉状态
    loosing.value = false;
    enableToRefresh.value = true;
  }, animationDuration) as any) as number;
}
</script>

<style>
.srrryration {
  transition-property: transform, height, opacity;
  transition-timing-function: ease;
  transition-duration: 0.25s;
}

.srrryrationOn {
  transform: rotate(0deg);
}

.srrryrationOf {
  transform: rotate(180deg);
}
</style>
<style scoped>
.scroyy {
  overflow: hidden;
  /* #ifndef APP-NVUE */
  max-height: 100vh;
  /* #endif */
}

.scroyy__track {
  position: relative;
}

.scroyy__track--loosing {
  transition-property: transform, height, opacity;
  transition-timing-function: ease;
  transition-duration: 0.35s;
}

.scroyy__tips {
  position: absolute;
  color: #bbb;
  font-size: 24rpx;
  top: 0;
  width: 100%;
  transform: translateY(-100%);
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
  overflow: hidden;
}

.scroyy__text {
  margin: 16rpx 0 0;
}

.scroyy__wrap {
  position: relative;
}
</style>
